﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Eventable;

namespace EventableTest
{
    [TestClass]
    public class TestUnits
    {
        public struct Helpers
        {
            [RegisterEvents("Yahoo!", "Bang!")]
            public class Simple
            {
                public Simple()
                {

                }
            }

            [RegisterEvents("Yahoo!", "Bang!")]
            [RegisterChangeEvents("Code")]
            public class EventProviderProxy : IEventProvider
            {
                public EventProviderProxy()
                {
                    evntMan = new EventProvider(this);                    
                }

                #region EventAggregate
                private EventProvider evntMan;

                public IEnumerable<string> GetDeclaredEvents()
                {
                    return evntMan.GetDeclaredEvents();
                }

                public void ListenEvent(object keyObj, EvntHandler handler, params string[] nameEvents)
                {
                    evntMan.ListenEvent(keyObj, handler, nameEvents);
                }

                public void UnlistenEvent(object keyObj, EvntHandler handler, params string[] nameEvents)
                {
                    evntMan.UnlistenEvent(keyObj, handler, nameEvents);
                }

                public void UnlistenEvent(object keyObj, params string[] nameEvents)
                {
                    evntMan.UnlistenEvent(keyObj, nameEvents);
                }

                public bool NotifyEvent(string nameEvent, params object[] args)
                {
                    return evntMan.NotifyEvent(nameEvent, args);
                }

                public void DettachObject(object keyObj)
                {
                    evntMan.DettachObject(keyObj);
                }

                public bool TryNotifyEvent(string nameEvent, out Exception ex, params object[] args)
                {
                    return evntMan.TryNotifyEvent(nameEvent, out ex, args);
                }

                public bool TryNotifyEvent(string nameEvent, params object[] args)
                {
                    return evntMan.TryNotifyEvent(nameEvent, args);
                }

                public bool TryAllNotifyEvent(string nameEvent, params object[] args)
                {
                    return evntMan.TryAllNotifyEvent(nameEvent, args);
                }

                public bool TryAllNotifyEvent(string nameEvent, out Exception[] exs, params object[] args)
                {
                    return evntMan.TryAllNotifyEvent(nameEvent, out exs, args);
                }

                #endregion
            }


            public class TestEventableProperties : IEventProvider, ISilentAccess
            {
                private long _Volume;
                /// <summary>
                /// Ёмкость
                /// </summary>
                [RegisterPropertyChange(ChangeEvents.ChangingAndChanged)]
                public virtual long Volume
                {
                    get { return _Volume; }
                    set
                    {
                        evntMan.SetValueProperty<long>("Volume", value, n => _Volume = n, () => Volume, default(long));
                    }
                }


                private long _Size;
                /// <summary>
                /// Размер
                /// </summary>
                [RegisterPropertyChange(ChangeEvents.ChangingAndChanged)]
                public virtual long Size
                {
                    get { return _Size; }
                    set
                    {
                        evntMan.SetValueProperty<long>("Size", value, n => _Size = n, () => Size, default(long));
                    }
                }

                /// <summary>
                /// Свободное место
                /// </summary>
                [RegisterPropertyChange(ChangeEvents.ChangingAndChanged)]
                public virtual long FreeSpace
                {
                    get
                    {
                        return Volume - Size;
                    }
                    set
                    {
                    }
                }

                public TestEventableProperties()
                {
                    evntMan = new EventProviderSpec(this);
                    Contracts.Property.Depend<long, long>(this, "Size", "FreeSpace", n => Volume - n);
                    Contracts.Property.Restrict<long>(this, "FreeSpace", s => s >= 0, () => new Helpers.TestException());
                }

                #region EventAggregate
                private EventProviderSpec evntMan;

                public IEnumerable<string> GetDeclaredEvents()
                {
                    return evntMan.GetDeclaredEvents();
                }

                public void ListenEvent(object keyObj, EvntHandler handler, params string[] nameEvents)
                {
                    evntMan.ListenEvent(keyObj, handler, nameEvents);
                }

                public void UnlistenEvent(object keyObj, EvntHandler handler, params string[] nameEvents)
                {
                    evntMan.UnlistenEvent(keyObj, handler, nameEvents);
                }

                public void UnlistenEvent(object keyObj, params string[] nameEvents)
                {
                    evntMan.UnlistenEvent(keyObj, nameEvents);
                }

                public bool NotifyEvent(string nameEvent, params object[] args)
                {
                    return evntMan.NotifyEvent(nameEvent, args);
                }

                public void DettachObject(object keyObj)
                {
                    evntMan.DettachObject(keyObj);
                }

                public bool TryNotifyEvent(string nameEvent, out Exception ex, params object[] args)
                {
                    return evntMan.TryNotifyEvent(nameEvent, out ex, args);
                }

                public bool TryNotifyEvent(string nameEvent, params object[] args)
                {
                    return evntMan.TryNotifyEvent(nameEvent, args);
                }

                public bool TryAllNotifyEvent(string nameEvent, params object[] args)
                {
                    return evntMan.TryAllNotifyEvent(nameEvent, args);
                }

                public bool TryAllNotifyEvent(string nameEvent, out Exception[] exs, params object[] args)
                {
                    return evntMan.TryAllNotifyEvent(nameEvent, out exs, args);
                }

                #endregion

                public bool IsUpdate()
                {
                    return evntMan.IsUpdate();
                }

                public IDisposable GetSilentAccess()
                {
                    return evntMan.GetSilentAccess();
                }
            }

            [Serializable]
            public class TestException : Exception
            {
                private static int Counter;
                private int _Number;
                public int Number
                {
                    get
                    {
                        if (_Number == 0)
                            _Number = ++Counter;
                        return _Number;
                    }
                    set { _Number = value; }
                }

                public TestException() { }
                public TestException(string message) : base(message) { }
                public TestException(string message, Exception inner) : base(message, inner) { }
                protected TestException(
                  System.Runtime.Serialization.SerializationInfo info,
                  System.Runtime.Serialization.StreamingContext context)
                    : base(info, context) { }
            }

            public static TestException GetTestException()
            {
                return new TestException();
            }

            public static string[] GetTestEventsNames()
            {
                return new string[] { "Yahoo!", "Bang!", "CodeChanging", "CodeChanged" };
            }

            public static string[] GetTestPropertyEvents()
            {
                return new string[] { "TestPropertyChanging", "TestProperty_pE", "TestPropertyChanged" };
            }
        }

        [TestMethod]
        [Description("Автоматическое создание прослушиваемых событий")]
        public void AutoBindingEvents()
        {
            var ep = new EventProvider(new Helpers.EventProviderProxy());
            var e = Helpers.GetTestEventsNames();
            var e2 = ep.GetDeclaredEvents();

            Assert.IsFalse(e2
                .Except(e)
                .Any(), "Список событий зарегистрирован с ошибкой.");
        }

        [TestMethod]
        [Description("Привязка прослушивания события")]
        public void BindingListenEvent()
        {
            var hlp = new Helpers.EventProviderProxy();
            using (var ep = new EventProvider(hlp))
            {
                var e = Helpers.GetTestEventsNames();
                var e1 = e.ToList();
                int cntCalls = e.Length;
                EvntHandler ehp = (sender, evName, args) => e1.Remove(evName) && cntCalls-- > int.MinValue;
                foreach (var item in e)
                    ep.ListenEvent(hlp, ehp, item);
                foreach (var item in e)
                    ep.NotifyEvent(item);
                Assert.IsTrue(cntCalls == 0, "Ошибка логики привязки/вызова событий");
            }
        }

        [TestMethod]
        [Description("Множественная привязка прослушивания события")]
        public void MultiBindingListenEvent()
        {
            var hlp = new Helpers.EventProviderProxy();
            using (var ep = new EventProvider(hlp))
            {
                var e = Helpers.GetTestEventsNames();
                int cntCalls = 4;
                EvntHandler ehp = (sender, evName, args) => cntCalls-- > int.MinValue;
                for (int i = 0; i < cntCalls; i++)
                    ep.ListenEvent(hlp, ehp, e[0]);
                ep.NotifyEvent(e[0]);
                Assert.IsTrue(cntCalls == 0, "Ошибка логики привязки/вызова событий");
            }
        }

        [TestMethod]
        [Description("Единичная отвязка")]
        public void OneBindingUnlistenEvent()
        {
            var hlp = new Helpers.EventProviderProxy();
            using (var ep = new EventProvider(hlp))
            {
                var e = Helpers.GetTestEventsNames();
                int cntCalls = e.Length;
                EvntHandler ehp = (sender, evName, args) => cntCalls-- > int.MinValue;
                for (int i = 0; i < cntCalls; i++)
                    ep.ListenEvent(hlp, ehp, e[0]);
                ep.UnlistenEvent(hlp, ehp, e[0]);
                ep.NotifyEvent(e[0]);
                Assert.IsTrue(cntCalls == 1, "Ошибка логики привязки/вызова событий");
            }
        }

        [TestMethod]
        [Description("Отвязка объекта")]
        public void DettachObjectTest()
        {
            var hlp = new Helpers.EventProviderProxy();
            using (var ep = new EventProvider(hlp))
            {
                var e = Helpers.GetTestEventsNames();
                int cntCalls = 0;
                EvntHandler ehp = (sender, evName, args) => cntCalls-- > int.MinValue;

                foreach (var item in e)
                    ep.ListenEvent(hlp, ehp, item);
                ep.ListenEvent(hlp, ehp, e[0]);

                ep.DettachObject(hlp);

                foreach (var item in e)
                    ep.NotifyEvent(item);
                ep.NotifyEvent(e[0]);

                Assert.IsTrue(cntCalls == 0, "Ошибка логики привязки событий");
            }
        }

        [TestMethod]
        [Description("Генерация исключения при возникновении события")]
        [ExpectedException(typeof(Helpers.TestException))]
        public void ThrowNotifyExceptionTest()
        {
            var hlp = new Helpers.EventProviderProxy();
            using (var ep = new EventProvider(hlp))
            {
                var e = Helpers.GetTestEventsNames();
                var testEx = Helpers.GetTestException();
                EvntHandler ehp = (sender, evName, args) => { throw Helpers.GetTestException(); };
                ep.ListenEvent(hlp, ehp, e[0]);
                ep.NotifyEvent(e[0]);
            }
        }

        [TestMethod]
        [Description("Возврат исключения при возникновении события")]
        [ExpectedException(typeof(Helpers.TestException))]
        public void InterceptNotifyException()
        {
            var hlp = new Helpers.EventProviderProxy();
            using (var ep = new EventProvider(hlp))
            {
                var e = Helpers.GetTestEventsNames();
                Exception tesEx = null;
                EvntHandler ehp = (sender, evName, args) => { throw Helpers.GetTestException(); };
                ep.ListenEvent(hlp, ehp, e[0]);
                Assert.IsFalse(ep.TryNotifyEvent(e[0], out tesEx), "Ошибка логики вызова событий");
                throw tesEx;
            }
        }

        [TestMethod]
        [Description("Проверка автосвойств с событием")]
        public void EventableAutoPropertyTest()
        {           
            
            var hlp = new Helpers.TestEventableProperties();
            var mutex = false;
            hlp.Volume = 10;
            hlp.ListenEvent(hlp, (sender, evName, args) => mutex, "VolumeChanging");
            hlp.Volume = 5;
            Assert.IsTrue(hlp.Volume == 10);            
            hlp.Size = hlp.Volume;
            Assert.IsTrue(hlp.Size == hlp.Volume);
            try
            {
                hlp.Size = hlp.Volume + 1;
            }
            catch (Helpers.TestException)
            {

            }
            catch (Exception)
            {
                throw;
            }
            Assert.IsTrue(hlp.Size == hlp.Volume);
        }

        [TestMethod]
        [Description("Проверка изменения свойства без оповещения")]
        public void SilentAccessTest()
        {
            var hlp = new Helpers.TestEventableProperties();
            var mutex = 0;
            hlp.Volume = 10;           
            hlp.ListenEvent(hlp, (sender, evName, args) => ++mutex > 0, "FreeSpaceChanged");
            hlp.Size = hlp.Volume;            
            using (hlp.GetSilentAccess())
                hlp.Size = hlp.Volume + 1;
            Assert.IsTrue(hlp.Size == hlp.Volume + 1);
            Assert.IsTrue(mutex == 2);
        }
    }
}
